package com.unittec.drainage.gateway.filter;

import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import com.unittec.drainage.gateway.common.GateWayConstants;
import com.unittec.drainage.gateway.common.GatewayUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.http.HttpStatus;
import org.apache.logging.log4j.util.Strings;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.stereotype.Component;
import org.springframework.web.util.UrlPathHelper;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.auth0.jwt.interfaces.Claim;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import com.unittec.drainage.gateway.common.ApplicationUtils;
import com.unittec.drainage.provider.common.JwtTokenUtil;
import com.unittec.drainage.provider.common.Md5;
import com.unittec.drainage.provider.common.ServiceFuncUtils;
import com.unittec.drainage.provider.common.WebUtils;
import com.unittec.drainage.provider.log.Log;
import com.unittec.drainage.provider.scope.ThreadLocalScope;
import com.unittec.drainage.provider.service.HystrixRestTemplateService;
import com.unittec.drainage.sdk.auth.request.Req101106;
import com.unittec.drainage.sdk.auth.request.Req101703;
import com.unittec.drainage.sdk.auth.request.Req101705;
import com.unittec.drainage.sdk.auth.response.Res101106;
import com.unittec.drainage.sdk.auth.response.Res101703;
import com.unittec.drainage.sdk.auth.response.Res101705;
import com.unittec.drainage.sdk.root.common.FieldConstants;
import com.unittec.drainage.sdk.root.domain.GatewayRequest;
import com.unittec.drainage.sdk.root.domain.Result;
import com.unittec.drainage.sdk.root.enums.BizCode;
import com.unittec.drainage.sdk.root.enums.LogLevelE;
import com.unittec.drainage.sdk.root.internal.utils.GatewayRequestParser;
import com.unittec.drainage.sdk.root.internal.utils.SdkUtils;

/**
 * 负责应用请求鉴权和限流
 */
@Component
public class GatewayAuthFilter extends ZuulFilter {

    @Autowired
    Log log;

    @Autowired
    private HystrixRestTemplateService hystrixRestTemplateService;

    @Override
    public String filterType() {
        return FilterConstants.PRE_TYPE;
    }

    /**
     * 应用校验 获取token，将token放在请求头中传入到业务服务中去
     *
     * @param ctx
     * @param appId
     * @param appKey
     * @return
     */
    private void checkAppAuth(RequestContext ctx, String appId, String appKey) {
        if (!ApplicationUtils.checkAppId(WebUtils.getIpAddress(ctx.getRequest()), appId, appKey)) {
            throw new RuntimeException("appID  错误");
        }
    }

    @Override
    public int filterOrder() {
        return FilterConstants.SEND_ERROR_FILTER_ORDER;
    }

    private final UrlPathHelper urlPathHelper = new UrlPathHelper();

    @Override
    public boolean shouldFilter() {
        return true;
    }

    private String tokenCheck(String token, RequestContext ctx) throws Exception {
        if (token.contains(FieldConstants.REQ_BEARER)) {
            token = token.substring(7);
        }

        Map<String, Claim> claimMap = JwtTokenUtil.verifyToken(token);

        String tenantId =
            claimMap.get(FieldConstants.TENANT_ID) == null ? "" : claimMap.get(FieldConstants.TENANT_ID).asString();
        String appCode =
            claimMap.get(FieldConstants.APP_CODE) == null ? "" : claimMap.get(FieldConstants.APP_CODE).asString();
        String appId =
            claimMap.get(FieldConstants.APP_ID) == null ? "" : claimMap.get(FieldConstants.APP_ID).asString();
        String user =
            claimMap.get(FieldConstants.LOGIN_USER) == null ? "" : claimMap.get(FieldConstants.LOGIN_USER).asString();
        String userName = claimMap.get(FieldConstants.LOGIN_USER_NAME) == null ? ""
            : claimMap.get(FieldConstants.LOGIN_USER_NAME).asString();
        // 管理员登录系统，token快过期需要更新
        // TODO：刷新的令牌需要通知到客户端
        // if (!JwtTokenUtil.checkToken(token)) {
        // token = JwtTokenUtil.createToken(user, tenantId, appCode);
        // // setCookietoken(ctx, token);
        // }
        ctx.addZuulRequestHeader(FieldConstants.TENANT_ID, tenantId);
        ctx.addZuulRequestHeader(FieldConstants.LOGIN_USER, user);
        ctx.addZuulRequestHeader(FieldConstants.LOGIN_USER_NAME, userName);
        ctx.addZuulRequestHeader(FieldConstants.APP_CODE, appCode);
        ctx.addZuulRequestHeader(FieldConstants.APP_ID, appId);
        ctx.addZuulRequestHeader(FieldConstants.REQ_AUTHORIZATION, FieldConstants.REQ_BEARER + token);
        ctx.getResponse().setHeader(FieldConstants.REQ_AUTHORIZATION, FieldConstants.REQ_BEARER + token);

        return token;
    }

    // private String appAuth(String token, RequestContext ctx, String ip) {
    // String reuestId = ctx.getZuulRequestHeaders().get(FieldConstants.REQUEST_ID);
    // GatewayRequest gatewayData = (GatewayRequest)ctx.get(GateWayConstants.SERVICECONTENT);
    //
    // if (StringUtils.isBlank((token))) {
    // // 调用鉴权服务的接口，获取token，将token放在请求头中传入到业务服务中去
    // String result = _appAuth(ctx, gatewayData.getAppId(), gatewayData.getAppKey());
    // if (null != result) {
    // return result;
    // }
    // } else {
    // // 检验用户携带的token是否有效，无效需要重新到认证服务鉴权
    // try {
    // tokenCheck(token, ctx);
    // } catch (Exception e) {
    // // 如果客户端携带了token，且没有app key就是管理员登录，只要token没失效就重新登录验证
    // if (!(Strings.isBlank(gatewayData.getAppKey()) && Strings.isBlank(gatewayData.getAppId()))) {
    // String result = _appAuth(ctx, gatewayData.getAppId(), gatewayData.getAppKey());
    //
    // String logmsg = String.format("reAuthCheck url=[%s] requestId=[%s] appId=[%s] result=[%s]",
    // gatewayData.getMethod(), reuestId, gatewayData.getAppId(), result);
    // log.sysErrLog("reAuthCheck", logmsg + e.toString(), e);
    //
    // if (null != result) {
    // return result;
    // }
    // } else {
    // return "用户token失效";
    // }
    // }
    // }
    //
    // ThreadLocalScope.setAuthData(gatewayData.getRequestId(),
    // ctx.getZuulRequestHeaders().get(FieldConstants.REQ_AUTHORIZATION.toLowerCase()), gatewayData.getAppId(),
    // ctx.getZuulRequestHeaders().get(FieldConstants.TENANT_ID.toLowerCase()), ip,
    // ctx.getZuulRequestHeaders().get(FieldConstants.LOGIN_USER.toLowerCase()),
    // ctx.getZuulRequestHeaders().get(FieldConstants.APP_CODE.toLowerCase()));
    //
    // return null;
    // }

    /**
     * @param ctx
     * @param account
     * @param password
     * @param loginType
     *            登录类型(app端-0,pc端-1)
     * @return
     */
    private String _userAuth(RequestContext ctx, String account, String password, int loginType) {
        String reuestId = ctx.getZuulRequestHeaders().get(FieldConstants.REQUEST_ID);
        GatewayRequest<Req101703> request = new GatewayRequest<>();
        request.setMethod("101703");
        request.setRequestId(reuestId);

        Req101703 content = new Req101703();
        content.setLoginType(String.valueOf(loginType));
        if (loginType == 1) {
            content.setUserName(account);
            content.setPassword(password);
        } else {
            content.setPhone(account);
            content.setCode(password);
        }
        request.setContent(content);
        ThreadLocalScope.AuthData authData = ThreadLocalScope.getAuthData();

        try {
            Result<Res101703> result = hystrixRestTemplateService.exchange(
                Md5.getMd5(FieldConstants.SYS_REQUEST_SOURCE_ID), GateWayConstants.USERAUTHURL, request, authData,
                new ParameterizedTypeReference<Result<Res101703>>() {});
            if (result.isSuccess()) {
                String token = result.getData().getToken();
                // setCookietoken(ctx, token);
                ctx.addZuulRequestHeader(FieldConstants.REQ_AUTHORIZATION, FieldConstants.REQ_BEARER + token);
                ctx.getResponse().setHeader(FieldConstants.REQ_AUTHORIZATION, FieldConstants.REQ_BEARER + token);
                ctx.addZuulRequestHeader(FieldConstants.APP_CODE, authData.getAppCode());
                ctx.addZuulRequestHeader(FieldConstants.TENANT_ID, result.getData().getTenantId());
                ctx.addZuulRequestHeader(FieldConstants.LOGIN_USER,
                    result.getData().getUserId() == null ? "" : result.getData().getUserId());
            } else {
                return result.getBizMsg();
            }
        } catch (Exception e) {
            return e.toString();
        }
        return null;
    }

    /**
     * 用户校验
     *
     * @param token
     * @param ctx
     * @param ip
     * @return
     */
    private String userAuth(String token, RequestContext ctx, String ip) {
        String reuestId = ctx.getZuulRequestHeaders().get(FieldConstants.REQUEST_ID);
        GatewayRequest gatewayData = (GatewayRequest)ctx.get(GateWayConstants.SERVICECONTENT);
        JSONObject content = (JSONObject)gatewayData.getContent();
        String username = content.getString("user_name");
        String password = content.getString("password");
        String phone = content.getString("phone");
        String code = content.getString("code");
        // 登录类型(app端-0,pc端-1)
        String loginType = content.getString("login_type");
        int logintype = 1;
        if (null != loginType) {
            logintype = Integer.valueOf(loginType);
        }
        // token = setCookietoken(ctx, token);
        String result = "";
        if (StringUtils.isBlank((token))) {
            if (!ServiceFuncUtils.isSysFun(gatewayData.getMethod())) {
                // 调用鉴权服务的接口，获取token，将token放在请求头中传入到业务服务中去
                if (null != loginType) {
                    if (logintype == 1) {
                        result = _userAuth(ctx, username, password, logintype);
                    } else {
                        result = _userAuth(ctx, phone, code, logintype);
                    }
                } else {
                    result = _userAuth(ctx, username, password, logintype);
                }

                if (null != result) {
                    return result;
                }
            }
            ThreadLocalScope.setAuthData(gatewayData.getRequestId(),
                ctx.getZuulRequestHeaders().get(FieldConstants.REQ_AUTHORIZATION.toLowerCase()), gatewayData.getAppId(),
                ctx.getZuulRequestHeaders().get(FieldConstants.TENANT_ID.toLowerCase()), ip,
                ctx.getZuulRequestHeaders().get(FieldConstants.LOGIN_USER.toLowerCase()),
                ctx.getZuulRequestHeaders().get(FieldConstants.APP_CODE.toLowerCase()),
                ctx.getZuulRequestHeaders().get(FieldConstants.LOGIN_USER_NAME.toLowerCase()));
        } else {
            // 检验用户携带的token是否有效，无效需要重新到认证服务鉴权
            try {
                tokenCheck(token, ctx);
                ThreadLocalScope.setAuthData(gatewayData.getRequestId(),
                    ctx.getZuulRequestHeaders().get(FieldConstants.REQ_AUTHORIZATION.toLowerCase()),
                    gatewayData.getAppId(), ctx.getZuulRequestHeaders().get(FieldConstants.TENANT_ID.toLowerCase()), ip,
                    ctx.getZuulRequestHeaders().get(FieldConstants.LOGIN_USER.toLowerCase()),
                    ctx.getZuulRequestHeaders().get(FieldConstants.APP_CODE.toLowerCase()),
                    ctx.getZuulRequestHeaders().get(FieldConstants.LOGIN_USER_NAME.toLowerCase()));
                String serviceName = "";
                if (ctx.get(GateWayConstants.SERVICEMETHED) != null) {
                    serviceName = String.valueOf(ctx.get(GateWayConstants.SERVICEMETHED));
                }
                if ((!GateWayConstants.INTERFACE_101705.equalsIgnoreCase(serviceName))
                    && StringUtils.isNotBlank(serviceName)) {
                    String result2 = checkPermissionsByperms(gatewayData.getMethod(), ctx);
                    if (StringUtils.isNotBlank(result2)) {
                        return result2;
                    }
                }

            } catch (Exception e) {
                // String result = _userAuth(ctx, username, password, logintype);

                String logmsg = String.format("reAuthCheck url=[%s] requestId=[%s] username=[%s] ",
                    gatewayData.getMethod(), reuestId, username);
                log.sysErrLog("reAuthCheck", logmsg + e.toString(), e);
                // return e.toString();
                // if (null != result) {
                // return result;
                // }
                ctx.setResponseStatusCode(HttpStatus.SC_UNAUTHORIZED);
                // GatewayUtils.writer(ctx.getResponse(), HttpStatus.SC_UNAUTHORIZED,
                // SdkUtils.toJSON(Result.error(gatewayData, BizCode.TOKEN_EXPIRE)));
            }
        }
        return null;
    }

    /**
     * 校验菜单权限编码
     *
     * @param method
     * @return
     */
    @SuppressWarnings("unchecked")
    private String checkPermissionsByperms(String method, RequestContext ctx) {
        GatewayRequest<Req101705> request = (GatewayRequest<Req101705>)ctx.get(GateWayConstants.SERVICECONTENT);
        request.setMethod("101705");
        Req101705 req101705 = new Req101705();
        req101705.setPerms(method);
        request.setContent(req101705);
        ThreadLocalScope.AuthData authData = ThreadLocalScope.getAuthData();
        Result<Res101705> result = hystrixRestTemplateService.exchange(Md5.getMd5(FieldConstants.SYS_REQUEST_SOURCE_ID),
            GateWayConstants.CHECK_METHOD_AUTH_URL, request, authData,
            new ParameterizedTypeReference<Result<Res101705>>() {});
        if (result.isSuccess()) {
            if (result.getData().getIsSuccess()) {
                return null;
            } else {
                return result.getData().getMessage();
            }
        } else {

            return result.getBizMsg();
        }
    }

    // private String setCookietoken(RequestContext ctx, String token) {
    // HttpServletRequest request = ctx.getRequest();
    // if (StringUtils.isBlank((token))) {
    // if (null != request.getCookies()) {
    // for (Cookie cookie : request.getCookies()) {
    // if (cookie.getName().equals(FieldConstants.REQ_AUTHORIZATION)) {
    // token = cookie.getValue();
    // }
    // }
    // }
    // } else {
    // String requestURI = this.urlPathHelper.getPathWithinApplication(ctx.getRequest());
    // if (token.contains(FieldConstants.REQ_BEARER)) {
    // token = token.substring(7);
    // }
    // Cookie cookie = new Cookie(FieldConstants.REQ_AUTHORIZATION, token);
    // cookie.setPath("/" + requestURI.split("/")[1]);
    // ctx.getResponse().addCookie(cookie);
    // }
    //
    // return token;
    // }

    // 鉴权和限流操作
    private String authCheck(RequestContext ctx) throws ZuulException {
        GatewayRequest gatewayData = null;
        HttpServletRequest request = ctx.getRequest();
        String reuestId = SdkUtils.getResultId();
        String ip = WebUtils.getIpAddress(request);

        String token = request.getHeader(FieldConstants.REQ_AUTHORIZATION);
        if (StringUtils.isBlank((token))) {
            token = request.getParameter(FieldConstants.REQ_AUTHORIZATION);
        }

        try {
            if (("POST").equalsIgnoreCase(request.getMethod())) {
                gatewayData = GatewayRequestParser.postData(request);
            } else {
                gatewayData = GatewayRequestParser.getData(request);
            }
        } catch (Exception e) {
            e.printStackTrace();
            log.sysErrLog("authCheck", "parse request content is error.", e);
            return e.toString();
        }
        gatewayData.setRequestId(reuestId);
        ctx.set(GateWayConstants.SERVICECONTENT, gatewayData);
        ctx.addZuulRequestHeader(FieldConstants.REQUEST_ID, reuestId);

        if (StringUtils.isBlank((token))) {
            token = gatewayData.getToken();
        }

        // 消息签名校验
        if (null != gatewayData.getSign() && !gatewayData.validateSign(gatewayData.getSign())) {
            return "消息签名校验错误";
        }

        if (Strings.isBlank(gatewayData.getMethod())) {
            return "接口为空";
        }

        log.sysInfoLog("authCheck", "reuestId=[{}] appId=[{}] method=[{}]", reuestId, gatewayData.getAppId(),
            gatewayData.getMethod());

        ctx.set(GateWayConstants.SERVICEMETHED, gatewayData.getMethod());

        // 新用户注册和新应用开通，不走正常的鉴权流程，网关只是转发一下
        if (GateWayConstants.IGORE_METHOD.contains(gatewayData.getMethod())) {
            ThreadLocalScope.setAuthData(gatewayData.getRequestId(), gatewayData.getAppId(), ip);
            return null;
        }

        // 管理员登录后台系统
        // if ("10027".equals(gatewayData.getMethod())) {
        // ThreadLocalScope.setAuthData(gatewayData.getRequestId(), gatewayData.getAppId(), ip);
        // return userAuth(token, ctx, ip);
        // }
        // token = setCookietoken(ctx, token);

        // 101501 查询应用列表
        // if (!"101501".equals(gatewayData.getMethod()) && Strings.isBlank(gatewayData.getAppId())) {
        // return "应用编号为空";
        // }

        if (Strings.isBlank(token) && Strings.isBlank(gatewayData.getAppKey())) {
            return "应用秘钥为空";
        }

        ThreadLocalScope.setAuthData(gatewayData.getRequestId(), gatewayData.getAppId(), ip);
        ctx.addZuulRequestHeader(FieldConstants.APP_ID, gatewayData.getAppId());
        ctx.addZuulRequestHeader(FieldConstants.REQUEST_ID, reuestId);
        // 应用鉴权
        checkAppAuth(ctx, gatewayData.getAppId(), gatewayData.getAppKey());
        return userAuth(token, ctx, ip);
    }

    // 其它的非SDK的访问，直接用zuul的原生路由配置
    private Object _run(RequestContext ctx) {
        ctx.set(GateWayConstants.CANRUN, false);

        HttpServletRequest request = ctx.getRequest();
        String token = request.getParameter(FieldConstants.REQ_AUTHORIZATION);
        if (StringUtils.isBlank((token))) {
            token = request.getHeader(FieldConstants.REQ_AUTHORIZATION);
        }

        if (null != token && token.indexOf(FieldConstants.REQ_BEARER) > -1) {
            token = token.substring(7);
        }

        // token = setCookietoken(ctx, token);

        if (StringUtils.isBlank((token))) {
            ctx.setResponseStatusCode(HttpStatus.SC_FORBIDDEN);
            // ctx.setResponseStatusCode(HttpStatus.SC_UNAUTHORIZED);
            // ctx.setSendZuulResponse(false);
            // ctx.set(GateWayConstants.CANRUN, false);
        } else {
            ctx.addZuulRequestHeader(FieldConstants.REQ_AUTHORIZATION, FieldConstants.REQ_BEARER + token);
        }

        return null;
    }

    @Override
    public Object run() throws ZuulException {
        RequestContext ctx = RequestContext.getCurrentContext();
        // 记录网关接收到请求消息的时间
        ctx.set(GateWayConstants.REQ_START_TIME, System.currentTimeMillis());

        String requestUri = this.urlPathHelper.getPathWithinApplication(ctx.getRequest());
        // 其它的非SDK的访问，直接用zuul的原生路由配置
        if (requestUri.indexOf("/gateway") == -1) {
            return _run(ctx);
        }

        String ret = authCheck(ctx);

        GatewayRequest req = (GatewayRequest)ctx.get(GateWayConstants.SERVICECONTENT);
        String token = ctx.getZuulRequestHeaders().get(FieldConstants.REQ_AUTHORIZATION.toLowerCase());
        req.setToken(token);

        if (Strings.isBlank(ret)) {
            ctx.set(GateWayConstants.CANRUN, true);
            if (log.checkLogLevel(req, LogLevelE.DEBUG.getLevel())) {
                log.debug(req, "gateway receive correct req msg=【{}】", JSON.toJSONString(req));
            }
        } else {
            if (log.checkLogLevel(req, LogLevelE.DEBUG.getLevel())) {
                log.debug(req, "gateway receive incorrect req, error=【{}】 msg=【{}】", ret, JSON.toJSONString(req));
            }

            // 过滤该请求，不对其进行路由
            ctx.setSendZuulResponse(false);
            ctx.setResponseStatusCode(HttpStatus.SC_OK);
            ctx.set(GateWayConstants.CANRUN, false);
            GatewayUtils.writer(ctx.getResponse(), SdkUtils.toJSON(Result.getewayError(req, BizCode.ERROR, ret)));
        }

        return null;
    }

    /**
     * 根据用户ID获取用户名
     *
     * @param userId
     * @return
     */
    private String getUserNameByUserId(String userId) {
        String userNameDb = "";
        GatewayRequest<Req101106> getWay = new GatewayRequest<>();
        Req101106 req101106 = new Req101106();
        req101106.setId(userId);
        getWay.setMethod("101106");
        getWay.setContent(req101106);
        Result<Res101106> result101106 = hystrixRestTemplateService.exchange(
            Md5.getMd5(FieldConstants.SYS_REQUEST_SOURCE_ID), GateWayConstants.AUTH_101106, getWay,
            ThreadLocalScope.getAuthData(), new ParameterizedTypeReference<Result<Res101106>>() {});
        if (result101106.isSuccess()) {
            userNameDb = result101106.getData().getUsername();
        }
        return userNameDb;
    }

}
